
import java.util.ArrayList;
import java.util.List;

import ml.practicum.feature.LVFeature;
import ml.practicum.learn.BasicPerceptron;
import ml.practicum.learn.Learner;
import ml.practicum.feature.Feature.FeatureRole;
import ml.practicum.learn.Perceptron;
import ml.practicum.logistic.BasicLV;
import ml.practicum.table.DynamicHeadedTable;
import ml.practicum.table.HeadedTable;
import ml.practicum.util.Percist;

public class GallantPocketLearner implements Learner<BasicPerceptron<BasicLV>, HeadedTable<LVFeature<String>,String>> {		

	// This is not a pretty solution there should a data type or 2 seperate functions getting data
	// or something else
	List<BasicLV> in;
	BasicLV out;
	double bestScore;
	BasicPerceptron<BasicLV> pocket;
	HeadedTable<LVFeature<String>,String> data;
	int bestRun;
	int currentRun;
	
	public GallantPocketLearner() {
		super();
		in = null;
		out =null;
		bestScore = 0;
		pocket= null;
		bestRun = 0;
		currentRun = 0;
	}


	@SuppressWarnings("unchecked")
	public BasicPerceptron<BasicLV> learn
	(
			BasicPerceptron<BasicLV> model,
			HeadedTable<LVFeature<String>, String> data
	)
	{
		this.data = data;
		pocket= (BasicPerceptron<BasicLV>) Percist.clone(model);
		BasicPerceptron<BasicLV > result = model; 
		for(List<String> row:data.getRows()){
			setInAndOut(data.getHeader(),row);
			result = learn(result,in,out);
		}		
		return pocket;
	}


	private void setInAndOut
	(
			List<LVFeature<String>> header,
			List<String> row
	)
	{
		in = new ArrayList<BasicLV>();
		for(int i =0; i<row.size();i++ ){
			if(header.get(i).getRole() == FeatureRole.INPUT){
				in.addAll(header.get(i).processValue(row.get(i)));
			}else if (header.get(i).getRole() == FeatureRole.OUTPUT){
				out = header.get(i).processValue(row.get(i)).get(0);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private BasicPerceptron<BasicLV> learn
	(
			BasicPerceptron<BasicLV> model,
			List<BasicLV> input,
			BasicLV output
	)
	{
		double d = output.doubleValue() - model.calculate(input).doubleValue();	
		if(Math.abs(d)>0.5){
			for(int i= 0; i < input.size(); i++){
				model.getWeights().set(i, model.getWeights().get(i) + d*input.get(i).doubleValue()) ;
			}
			currentRun = 0;
		}else{
			currentRun++;
			if(currentRun > bestRun){
				bestRun = currentRun;
				if(pocket != model){
					pocket = (BasicPerceptron<BasicLV>) Percist.clone(model);
				}
			}
		}
			return model;
		}

		@SuppressWarnings("unchecked")
		public HeadedTable<LVFeature<String>, String> calculate
		(
				BasicPerceptron<BasicLV> model,
				HeadedTable<LVFeature<String>, String> data
		) 
		{
			LVFeature<String> outputFeature = getOutputFeature(data.getHeader());
			DynamicHeadedTable<LVFeature<String>,String> result = new DynamicHeadedTable(outputFeature);
			for(List<String> row:data.getRows()){
				setInAndOut(data.getHeader(),row);
				String[] resultRow = new String[1];
				resultRow[0] = outputFeature.getKey(model.calculate(in));
				result.addRow(resultRow);
			}
			return result;
		}

		private LVFeature<String> getOutputFeature
		(
				List<LVFeature<String>> header
		)
		{
			LVFeature<String> result = null;
			for(LVFeature<String> feature:header){
				if(feature.getRole() == FeatureRole.OUTPUT){
					result = feature;
				}
			}
			return result;
		}


		public double test
		(
				BasicPerceptron<BasicLV> model,
				HeadedTable<LVFeature<String>,String> data
		) 
		{
			double result = 0; 
			for(int i =0; i< data.getRowCount();i++){
				this.setInAndOut(data.getHeader(), data.getRow(i));	
				if (correct(model))result++ ;
			}		


			return result/data.getRowCount();
		}

		private boolean correct
		(
				Perceptron<BasicLV> model
		)
		{		
			return Math.abs(out.doubleValue() - model.calculate(in).doubleValue())<0.5;
		}

	}
